/**
 * Copyright 2011 Joe Choi, All rights reserved.
 */
package tao.calendar.gregorian;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import tao.time.AbstractTime;

/**
 * The ConcreteGrogorianDate class is an implementation of GregorianDate interface
 * 
 * @author Joe Choi
 * @version 1.0.0
 */
class ConcreteGregorianDate extends AbstractTime implements GregorianDate {
	/**
	 * Create a GregorianDate object from other Date object
	 */
	public ConcreteGregorianDate(GregorianDate date) {
		this(date.scalar());
	}
	
	/**
	 * Create a GregorianDate object from scalar value
	 */
	public ConcreteGregorianDate(long scalar) {
		construct(scalar);
	}
	
	/**
	 * Create a GregorianDate object by specifying the year, month and day
	 */
	public ConcreteGregorianDate(int year, int month, int day) {
		normalize(year, month, day);
	}

	/**
	 * Create a GregorianDate object by specifying the era, year, month and day
	 */
	public ConcreteGregorianDate(Era era, int year, int month, int day) {
		normalize(era, year, month, day);
	}

	@Override
	public long scalar() {
		Calendar c = getCalendar();
		c.set(Calendar.ERA, (getEra() == Era.AD ? GregorianCalendar.AD : GregorianCalendar.BC));
		c.set(getYear(), getMonth() - 1, getDay(), 0, 0, 0);
		c.set(Calendar.MILLISECOND, 0);
		return(c.getTimeInMillis());	
	}
	
	@Override
	public String toString() {
		return(getYear() + "/" + getMonth() + "/" + getDay() + " " + getEra());
	}
	
	@Override
	public int getDay() {
		return(day);
	}

	@Override
	public int getMonth() {
		return(month);	
	}

	@Override
	public int getYear() {
		return(year);
	}	
	
	@Override
	public Era getEra() {
		return(era);
	}

	protected void setInternalEra(Era era) {
		this.era = era;
	}
	
	protected void setInternalYear(int year) {
		if(year <= 0 || year > MAX_YEAR_VALUE) throw new IllegalArgumentException("Invlid year value[1-1 million]: " + year);
		this.year = year;
	}
	
	protected void setInternalMonth(int month) {
		if(month < 1 || month > 12) 
			throw new IllegalArgumentException("Invlid month value[1-12]: " + month);
		this.month = month;
	}

	public void setInternalDay(int day) {
		if(! isValidDayOfMonth(getYear(), getMonth(), day)) 
			throw new IllegalArgumentException("Invalid day value[1-31]: " + day);
		this.day = day;
	}
	
	protected void construct(long scalar) {
		scalar = truncateDayTime(scalar); // eliminate the millisecond of time in a day
		Calendar gmt = getCalendar();
		gmt.setTimeInMillis(scalar);
		setInternalEra(toEra(gmt.get(Calendar.ERA)));
		normalizeWithoutEra(gmt.get(Calendar.YEAR), gmt.get(Calendar.MONTH) + 1, gmt.get(Calendar.DAY_OF_MONTH));		
	}

	protected void normalizeWithoutEra(int year, int month, int day) {
		year = Normalizer.moduleNonZero(year, MAX_YEAR_VALUE);
		month = Normalizer.moduleNonZero(month, 12);
		day = Normalizer.moduleNonZero(day, numberDayOfMonth(year, month));
		
		set(year, month, day);		
	}

	protected boolean isValidDayOfMonth(int y, int m, int d) {
		return(d >= 1 && d <= numberDayOfMonth(y, m));
	}

	protected long truncateDayTime(long scalar) {
		return((scalar / GregorianTime.DAY_MILLIS) * GregorianTime.DAY_MILLIS);
	}

	protected Calendar getCalendar() {
		return(new GregorianCalendar(TimeZone.getTimeZone("GMT+00:00")));
	}
		
	private void set(int year, int month, int day) {
		setInternalYear(year);
		setInternalMonth(month);
		setInternalDay(day);
	}
	
	private Era toEra(int val) {
		return(val == GregorianCalendar.AD ? Era.AD : Era.BC);
	}
	
	private int numberDayOfMonth(int yr, int mnt) {
		int nDay = 0;
		switch(mnt) {
			case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
				nDay = 31; break;
			case 4: case 6: case 9: case 11: 
				nDay = 30; break;
			case 2:
				nDay = (new GregorianCalendar().isLeapYear(yr)) ? 29 : 28;
				break;
			default:
				throw new RuntimeException("Invalid month: " + mnt);
		}
		return(nDay);		
	}

	private void normalize(Era era, int year, int month, int day) {
		year = (year < 0 ? year * -1 : (year == 0) ? 1 : year);
		normalizeWithoutEra(year, month, day);
		setInternalEra(era);
	}
	
	private void normalize(int year, int month, int day) {
		setInternalEra((year > 0 ? Era.AD : Era.BC));
		year = (year > 0 ? year : (year * -1) + 1);
		normalizeWithoutEra(year, month, day);
	}
	
	private int year, month, day;
	private Era era;
	
	protected static final int MAX_YEAR_VALUE=1000000; 
}